package karma.pool.pool;

import karma.pool.util.clock.ClockFactory;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

import static karma.pool.util.IBagEntry.state_not_used;

/**
 * The house keeping task to retire and maintain minimum idle connections.
 */
@Slf4j
public final class HouseKeeper implements Runnable {
   private Pool pool;
   private volatile long previous = ClockFactory.plusMillis(ClockFactory.currentTime(), -pool.housekeepingPeriodMs);

   public HouseKeeper(Pool pool) {
      this.pool = pool;
   }

   @Override
   public void run() {
      try {
         // refresh values in case they changed via MBean
         pool.connectionTimeout = pool.configuration.getConnectionTimeout();
         pool.validationTimeout = pool.configuration.getValidationTimeout();
         pool.proxyLeakReportRunnableFactory.updateLeakDetectionThreshold(pool.configuration.getLeakDetectionThreshold());
         pool.catalog = (pool.configuration.getCatalog() != null && !pool.configuration.getCatalog().equals(pool.catalog)) ? pool.configuration.getCatalog() : pool.catalog;

         final long idleTimeout = pool.configuration.getIdleTimeout();
         final long now = ClockFactory.currentTime();

         // Detect retrograde time, allowing +128ms as per NTP spec.
         if (ClockFactory.plusMillis(now, 128) < ClockFactory.plusMillis(previous, pool.housekeepingPeriodMs)) {
            log.warn("{} - Retrograde clock change detected (housekeeper delta={}), soft-evicting connections from pool.",
               pool.poolName, ClockFactory.elapsedDisplayString(previous, now));
            previous = now;
            pool.softEvictConnections();
            return;
         } else if (now > ClockFactory.plusMillis(previous, (3 * pool.housekeepingPeriodMs) / 2)) {
            // No point evicting for forward clock motion, this merely accelerates proxyConnection retirement anyway
            log.warn("{} - Thread starvation or clock leap detected (housekeeper delta={}).", pool.poolName, ClockFactory.elapsedDisplayString(previous, now));
         }

         previous = now;

         String afterPrefix = "Pool ";
         if (idleTimeout > 0L && pool.configuration.getMinimumIdle() < pool.configuration.getMaximumPoolSize()) {
            pool.logPoolState("Before cleanup ");
            afterPrefix = "After cleanup  ";
            //state_not_used
            final List<PoolEntry> poolEntryList = pool.bag.values(state_not_used);
            int toRemove = poolEntryList.size() - pool.configuration.getMinimumIdle();
            for (PoolEntry entry : poolEntryList) {
               if (toRemove > 0 && ClockFactory.elapsedMillis(entry.lastAccessed, now) > idleTimeout && pool.bag.reserve(entry)) {
                  pool.closeConnection(entry, "(proxyConnection has passed idleTimeout)");
                  toRemove--;
               }
            }
         }

         pool.logPoolState(afterPrefix);

         pool.fillPool(); // Try to maintain minimum connections
      } catch (Exception e) {
         log.error("Unexpected exception in housekeeping task", e);
      }
   }
}
